package com.hmall.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.common.utils.UserContext;
import com.hmall.domain.po.UserPreference;
import com.hmall.domain.vo.RecommendVO;
import com.hmall.mapper.UserPreferenceMapper;
import com.hmall.service.IItemService;
import com.hmall.service.IUserPreferenceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;

@Slf4j
@Service
@RequiredArgsConstructor
public class UserPreferenceServiceImpl extends ServiceImpl<UserPreferenceMapper, UserPreference> implements IUserPreferenceService {

    private final IItemService itemService;

    @Override
    public List<RecommendVO> getRecommdations() {
        // 1. 构建用户-商品评分矩阵 userId -> {itemId -> rating}
        Map<Long, Map<Long, Integer>> userRatings = new HashMap<>();

        // 2. 给用户-商品评分矩阵添加数据，从数据库中获取
        List<UserPreference> allUserPreferences = list();

        // 2.1 将 allUserPreferences 里面的数据保存到 userRatings
        for (UserPreference userPreference : allUserPreferences) {
            // 获取或创建用户对应的评分映射
            Map<Long, Integer> ratingsForUser = userRatings.computeIfAbsent(
                    userPreference.getUserId(),
                    k -> new HashMap<>()
            );

            // 更新该用户的商品评分
            ratingsForUser.put(userPreference.getItemId(), userPreference.getRating());
        }

        // 3.获取当前的用户id
        Long userId = UserContext.getUser();

        // 4.计算当前用户和其他用户的相似度值
        // 4.1 获取当前用户对商品的喜好值 itemId->rating
        Map<Long,Integer> userRatedItems = userRatings.get(userId);
        // 4.2 记录当前用户与其他用户的相似度分数    其他用户的userId-> 相似度
        Map<Long,Double> similarityScores = new HashMap<>();
        // 4.3计算当前用户的其他用户的相似度分数
        for (Long otherUser : userRatings.keySet()) {
            if (!otherUser.equals(userId)) {
                double similarity = calculateSimilarity(userRatedItems, userRatings.get(otherUser));
                similarityScores.putIfAbsent(otherUser, similarity);
                log.info("用户【{}】和用户【{}】的相似度 => {}", userId, otherUser, similarity);
            }
        }

        // 5.将相似度进行排序，相似度值从高到低进行排序
        List<Map.Entry<Long, Double>> sortedSimilarities = new ArrayList<>(similarityScores.entrySet());
        sortedSimilarities.sort((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()));

        // 6.获取推荐商品
        // 6.1对于每一个相似用户，检查他们评价过但当前用户未评价的商品，并基于相似度权重增加这些商品的推荐分数。
        // 6.1.1存储被推荐商品的得分,productId -> rating
        Map<Long, Double> itemRecommendationScores = new HashMap<>();
        // 6.1.2对已经排好序的当前用户与其他用户的相似度map 进行遍历
        for (Map.Entry<Long, Double> entry : sortedSimilarities) {
            Long otherUserId = entry.getKey();
            double weight = entry.getValue();   // 权重，即评分rating

            for (Map.Entry<Long, Integer> itemEntry : userRatings.get(otherUserId).entrySet()) {
                Long productId = itemEntry.getKey();
                // 只推荐没有评分过的商品
                if (!userRatedItems.containsKey(productId)) {
                    // 将该商品的推荐分数更新为原有的推荐分数加上其他用户对该商品的评分乘以他们的相似度权重
                    itemRecommendationScores.putIfAbsent(productId, itemRecommendationScores.getOrDefault(productId, 0.0) + weight * itemEntry.getValue());
                }
            }
        }
        // 6.2 对推荐商品进行排序并返回
        List<Map.Entry<Long, Double>> recommendedItems = new ArrayList<>(itemRecommendationScores.entrySet());
        recommendedItems.sort((entry1, entry2) -> entry2.getValue().compareTo(entry1.getValue()));
        List<Long> recommendations = new ArrayList<>(); //记录了被推荐的商品的id
        for (Map.Entry<Long, Double> entry : recommendedItems) {
            recommendations.add(entry.getKey());
        }

        // 7.构建响应
        List<RecommendVO> recommendVOS = new ArrayList<>();
        for(Long id : recommendations){
            RecommendVO rvo = new RecommendVO();
            rvo.setItemId(id);
            rvo.setName(itemService.getById(id).getName());
            rvo.setSold(itemService.getById(id).getSold());
            rvo.setPrice(itemService.getById(id).getPrice());
            rvo.setCommentCount(itemService.getById(id).getCommentCount());
            rvo.setImage(itemService.getById(id).getImage());

            recommendVOS.add(rvo);
        }
        return recommendVOS;

    }

    /**
     * <p>计算用户之间的余弦相似度</p>
     * <p>请求参数：productId -> rating</p>
     * @param user1Ratings  用户1的评分记录
     * @param user2Ratings  用户2的评分记录
     * @return  返回用户之间的相似度
     */
    private double calculateSimilarity(Map<Long, Integer> user1Ratings, Map<Long, Integer> user2Ratings) {
        // 获取两个用户评分中共有的商品
        Set<Long> commonItems = new HashSet<>(user1Ratings.keySet());
        commonItems.retainAll(user2Ratings.keySet());

        if (commonItems.isEmpty()) {
            return 0.0; // 如果没有共同商品，则相似度为0
        }

        double dotProduct = 0.0;    // 用于存储两个用户评分的点积结果
        double user1Magnitude = 0.0; // 用户1的评分的平方和
        double user2Magnitude = 0.0; // 用户2的评分的平方和

        // 遍历共同评分的商品并计算它们的点积
        for (Long item : commonItems) {
            int user1Rating = user1Ratings.get(item);
            int user2Rating = user2Ratings.get(item);
            dotProduct += user1Rating * user2Rating;
        }

        for (int rating : user1Ratings.values()) {
            user1Magnitude += rating * rating;
        }

        for (int rating : user2Ratings.values()) {
            user2Magnitude += rating * rating;
        }

        return dotProduct / (Math.sqrt(user1Magnitude) * Math.sqrt(user2Magnitude));
    }
}
